package edu.wpi.cs509.persistence;


import java.util.ArrayList;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

import edu.wpi.cs509.representation.UserAccount;
import edu.wpi.cs509.representation.UserStatistics;

/**
 * PersistenceDatabase extends Persistence and supports storing User information
 * in a remote database.
 * 
 * 
 * PLEASE DO NOT CHANGE THIS CODE WITHOUT TALKING TO adveres
 * 
 * @author adveres
 *
 */
public class PersistenceDatabase{
	
	/**
	 * Default constructor
	 */
	public PersistenceDatabase() {
	}
	
	/**
	 * Tests if a username is taken.
	 * 
	 * @param username the name to test
	 * @return true for yes false for no
	 */
	public static Boolean UsernameTaken(String username) {
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        
        Criteria c = session.createCriteria(UserAccount.class).add(Restrictions.eq("username", username));
        UserAccount player = (UserAccount)c.uniqueResult();
        
        session.getTransaction().commit();
        
        if( null == player ) {
        	return false;
        }
        else {
        	return true;
        }
	}

	/**
	 * Gets the rank of a player from the database. Returns -1 if not found.
	 * 
	 * This will return 0 for the first place person for ease-of-use. Even if "0th" place
	 * makes no sense in a conventional leaderboard.
	 * 
	 * @param username the username to search for
	 * @return ret -1 for not found or a nonnegative number for the rank
	 */
	public static int getRank(String username) {
		
		int ret = -1;

		//This should really check the entire scope of the database
		//but for this project there won't be thousands of accounts in the database.
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		session.beginTransaction();
		
		//Create criteria to get top 10 ratios in descending order
		Criteria crit = session.createCriteria(UserStatistics.class);
		crit.addOrder( Order.desc("ratio") );
		crit.setMaxResults(1000);
		
		@SuppressWarnings("unchecked")
		ArrayList<UserStatistics> highScoreList = new ArrayList<UserStatistics>( crit.list() );
		
		session.getTransaction().commit();
		
		//Linear search.  Sad, but true.
		for(int i=0; i<highScoreList.size(); i++) {
			if(highScoreList.get(i).getUsername().equals(username)) {
				ret = i;
				break;
			}
		}

		return ret;
		
	}
	/**
	 * Returns the top 25 players based on ratio in order.
	 * Index 0 should be the best player.
	 * @return highScoresList
	 * @throws exception
	 */
	public static ArrayList<UserStatistics> getHighScoresList() throws Exception {
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		session.beginTransaction();
		
		//Create criteria to get top 10 ratios in descending order
		Criteria crit = session.createCriteria(UserStatistics.class);
		crit.addOrder( Order.desc("ratio") );
		crit.setMaxResults(25);
		
		//Save it to an array list and return it
		@SuppressWarnings("unchecked")
		ArrayList<UserStatistics> highScoreList = new ArrayList<UserStatistics>( crit.list() );
		session.getTransaction().commit();
		
		System.out.println("getHighScoresList() returning.");

		return highScoreList;
	}
	
	/**
	 * Finds a user in the statistics database and [count] number of people behind them.  Returns this
	 * set of UserStatistics as a list.
	 * 
	 * TODO: Make this somehow return the rank of the name too
	 * 
	 * @param name the name to find
	 * @param count the number of people behind the given name
	 * @return the list including the name + people behind found name
	 * @throws Exception
	 */
	public static ArrayList<UserStatistics> getHighScoresList(String name, int count) throws Exception {
		
		System.out.println("Looking for a list starting with "+name);
		//If no name, return the default top 25 list
		if( name == null ) {
			return getHighScoresList();
		}
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		session.beginTransaction();
		
		//Create criteria to get ratios in descending order
		Criteria crit = session.createCriteria(UserStatistics.class);
		crit.addOrder( Order.desc("ratio") );
		
		//Save it to an array list and return it
		@SuppressWarnings("unchecked")
		ArrayList<UserStatistics> highScoreList = new ArrayList<UserStatistics>( crit.list() );
		session.getTransaction().commit();
		
		//Make a sublist and try to find the given name
		ArrayList<UserStatistics> subList;

		//Find where the user sits in our list. Error check.
		int index = findUsernameInList(highScoreList, name);
		
		//Find top number to grab
		int topNum = index+count+1;
		if( topNum > highScoreList.size() ) {
			topNum = highScoreList.size();
		}
		
		if( -1 != index) {
			subList = new ArrayList<UserStatistics>( highScoreList.subList(index, topNum) );
			}
		else {
			subList = null;
		}
		
		System.out.println("getHighScoresList(str,int) returning a sublist from "+index+" to "+topNum);

		return subList;
	}
	
	/**
	 * Finds rank [firstRank] from the database and [count] people after that and returns
	 * a list of those items.
	 * 
	 * @param firstRank the first rank to find
	 * @param count the number of people behind the given name
	 * @return the list including the name + people behind found name
	 * @throws Exception
	 */
	public static ArrayList<UserStatistics> getHighScoresList(int firstRank, int count) throws Exception {
		
		System.out.println("getHighScoresList(int,int) called with "+firstRank + " "+count);
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		session.beginTransaction();
		
		//Create criteria to get ratios in descending order
		Criteria crit = session.createCriteria(UserStatistics.class);
		crit.addOrder( Order.desc("ratio") );
		
		//Save it to an array list and return it
		@SuppressWarnings("unchecked")
		ArrayList<UserStatistics> highScoreList = new ArrayList<UserStatistics>( crit.list() );
		session.getTransaction().commit();
		
		//Check input for bounds
		if( (firstRank < 0) || (firstRank > highScoreList.size()) ) {
			return null;
		}
		
		//Find the top index to go to
		int topNum = firstRank + count + 1;
		if( topNum > highScoreList.size() ) {
			topNum = highScoreList.size();
		}

		System.out.println("getHighScoresList(int,int) returning a sublist from "+firstRank+" to "+topNum);
		
		return new ArrayList<UserStatistics>(highScoreList.subList(firstRank, topNum));
	}
	
	/**
	 * Given an arraylist and a username, return the index of the found name, or -1
	 * @param listToSearch the list to search
	 * @param nameToFind the name to find
	 * @return ret the index of the name, or -1 for not found
	 */
	private static int findUsernameInList(ArrayList<UserStatistics> listToSearch, String nameToFind) {
		int ret = -1;
		
		for(int i=0; i < listToSearch.size(); i++) {
			if( listToSearch.get(i).getUsername().equals(nameToFind) ) {
				ret = i;
				break;
			}
		}
		
		return ret;
	}
	
	/**
	 * Returns the statistics for a player based on player name
	 * 
	 * Will return null if nothing found.
	 * 
	 * @param name is the user name to find stats for
	 * @return outAccount the UserStatistic object containing stats
	 * @throws exception
	 */
	public static UserStatistics getUserStatistics(String username) {
		
		System.out.println("PD.getUserStatistics called with name: "+username);
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        
        Criteria c = session.createCriteria(UserStatistics.class).add(Restrictions.eq("username", username));
        UserStatistics outAccount = (UserStatistics)c.uniqueResult();
        session.getTransaction().commit();
        
        if(outAccount != null) {
        	System.out.println("getUserStatistics(str) returning stats for "+ username);
        }
        else {
        	System.out.println("getUserStatistics(str) returning null");
        }
        
        return outAccount;
	}
	
	/**
	 * Gets a user account from the database using Hibernate
	 * 
	 * returns null if nothing found
	 * 
	 * @param username the username to fetch the account for
	 * @return outAccount is the retrieved account
	 */
	public static UserAccount getUserAccount(String username) {
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        
        Criteria c = session.createCriteria(UserAccount.class).add(Restrictions.eq("username", username));
        UserAccount outAccount = (UserAccount)c.uniqueResult();
        session.getTransaction().commit();
        
        if(outAccount != null) {
        	System.out.println("getUserAccount(str) returning account for "+ username);
        }
        else {
        	System.out.println("getUserAccount(str) returning null");
        }
       
        return outAccount;
	}
	
	/**
	 * Deletes a row from the databases based on username. CAREFUL you don't wipe users needlessly!
	 * 
	 * @param username the username to delete the row for from the SQL database
	 * @throws exception
	 */
	public static void removeUserAccount(String username) throws Exception
	{
        UserAccount user = getUserAccount(username);
        
        if( null != user ) {
        	Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            
        	session.delete(user);
        	
        	System.out.println("PersistenceDatabase: Deleted ["+username+"] from the database.");
        	
            session.getTransaction().commit();
        }
        else {
        	System.out.println("PersistenceDatabase: ["+username+"] is not in the database to delete!");
        }
        
	}

}
